/**
 * 地球半径
 */
const EARTHRADIUS = 6370996.81;

export class Point {
  constructor(lng, lat) {
    this.lng = lng;
    this.lat = lat;
  }

  equals(point) {
    if (point instanceof Point) {
      return point.getLng() === this.lng && point.getLat() === this.lat;
    } else {
      return false;
    }
  }

  getLng() {
    return this.lng;
  }

  getLat() {
    return this.lat;
  }
}

export class Bounds {
  /**
   * @param swLng 西南脚点-经度
   * @param swLat 西南脚点-纬度
   * @param neLng 东北脚点-经度
   * @param neLat 东北脚点-纬度
   */
  constructor(swLng, swLat, neLng, neLat) {
    this.swLng = swLng;
    this.swLat = swLat;
    this.neLng = neLng;
    this.neLat = neLat;
    this.swPoint = new Point(swLng, swLat);
    this.nePoint = new Point(neLng, neLat);
  }

  getSouthWest() {
    return this.swPoint;
  }

  getNorthEast() {
    return this.nePoint;
  }
}

export class Polygon {
  /**
   * @param bounds 此类表示地理坐标的矩形区域。
   * @param path 多边型的点数组
   */
  constructor(bounds = [], path = []) {
    this.bounds = bounds;
    this.path = path;
  }

  setBounds(bounds) {
    this.bounds = bounds;
  }

  getBounds() {
    return this.bounds;
  }

  setPath(path) {
    this.path = path;
  }

  getPath() {
    return this.path;
  }
}

/**
 * @exports GeoUtils as BMapLib.GeoUtils
 */
const GeoUtils = function () {

};

/**
 * 判断点是否在矩形内
 * @param {Point} point 点对象
 * @param {Bounds} bounds 矩形边界对象
 * @returns {Boolean} 点在矩形内返回true,否则返回false
 */
GeoUtils.isPointInRect = function (point, bounds) {
  // 检查类型是否正确
  if (!(point instanceof Point) ||
    !(bounds instanceof Bounds)) {
    return false;
  }
  const sw = bounds.getSouthWest(); // 西南脚点
  const ne = bounds.getNorthEast(); // 东北脚点
  return (point.lng >= sw.lng && point.lng <= ne.lng && point.lat >= sw.lat && point.lat <= ne.lat);
}

/**
 * 判断点是否多边形内
 * @param {Point} point 点对象
 * @param {Polyline} polygon 多边形对象
 * @returns {Boolean} 点在多边形内返回true,否则返回false
 */
GeoUtils.isPointInPolygon = function (point, polygon) {
  // 检查类型
  if (!(point instanceof Point) ||
    !(polygon instanceof Polygon)) {
    return false;
  }

  // 首先判断点是否在多边形的外包矩形内，如果在，则进一步判断，否则返回false
  const polygonBounds = polygon.getBounds();
  if (!this.isPointInRect(point, polygonBounds)) {
    return false;
  }

  const pts = polygon.getPath(); // 获取多边形点

  // 下述代码来源：http://paulbourke.net/geometry/insidepoly/，进行了部分修改
  // 基本思想是利用射线法，计算射线与多边形各边的交点，如果是偶数，则点在多边形外，否则
  // 在多边形内。还会考虑一些特殊情况，如点在多边形顶点上，点在多边形边上等特殊情况。

  const N = pts.length;
  const boundOrVertex = true; // 如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
  let intersectCount = 0; // cross points count of x
  const precision = 2e-10; // 浮点类型计算时候与0比较时候的容差
  const p = point; // 测试点
  let p1 = pts[0];
  let p2; // neighbour bound vertices
  for (let i = 1; i <= N; ++i) { // check all rays
    if (p.equals(p1)) {
      return boundOrVertex; // p is an vertex
    }

    p2 = pts[i % N]; // right vertex
    if (p.lat < Math.min(p1.lat, p2.lat) || p.lat > Math.max(p1.lat, p2.lat)) { // ray is outside of our interests
      p1 = p2;
      continue; // next ray left point
    }

    if (p.lat > Math.min(p1.lat, p2.lat) && p.lat < Math.max(p1.lat, p2.lat)) { // ray is crossing over by the algorithm (common part of)
      if (p.lng <= Math.max(p1.lng, p2.lng)) { // x is before of ray
        if (p1.lat === p2.lat && p.lng >= Math.min(p1.lng, p2.lng)) { // overlies on a horizontal ray
          return boundOrVertex;
        }

        if (p1.lng === p2.lng) { // ray is vertical
          if (p1.lng === p.lng) { // o verlies on a vertical ray
            return boundOrVertex;
          } else { // before ray
            ++intersectCount;
          }
        } else { // cross point on the left side
          const xinters = (p.lat - p1.lat) * (p2.lng - p1.lng) / (p2.lat - p1.lat) + p1.lng; // cross point of lng
          if (Math.abs(p.lng - xinters) < precision) { // overlies on a ray
            return boundOrVertex;
          }

          if (p.lng < xinters) { // before ray
            ++intersectCount;
          }
        }
      }
    } else { // special case when ray is crossing through the vertex
      if (p.lat === p2.lat && p.lng <= p2.lng) { // p crossing over p2
        const p3 = pts[(i + 1) % N]; // next vertex
        if (p.lat >= Math.min(p1.lat, p3.lat) && p.lat <= Math.max(p1.lat, p3.lat)) { // p.lat lies between p1.lat & p3.lat
          ++intersectCount;
        } else {
          intersectCount += 2;
        }
      }
    }
    p1 = p2; // next ray left point
  }

  if (intersectCount % 2 === 0) { // 偶数在多边形外
    return false;
  } else { // 奇数在多边形内
    return true;
  }
}

/**
 * 将度转化为弧度
 * @param {degree} Number 度
 * @returns {Number} 弧度
 */
GeoUtils.degreeToRad = function (degree) {
  return Math.PI * degree / 180;
}

/**
 * 将弧度转化为度
 * @param {radian} Number 弧度
 * @returns {Number} 度
 */
GeoUtils.radToDegree = function (rad) {
  return (180 * rad) / Math.PI;
}

/**
 * 将v值限定在a,b之间，纬度使用
 */
function _getRange(v, a, b) {
  if (a != null) {
    v = Math.max(v, a);
  }
  if (b != null) {
    v = Math.min(v, b);
  }
  return v;
}

/**
 * 将v值限定在a,b之间，经度使用
 */
function _getLoop(v, a, b) {
  while (v > b) {
    v -= b - a
  }
  while (v < a) {
    v += b - a
  }
  return v;
}

/**
 * 计算两点之间的距离,两点坐标必须为经纬度
 * @param {point1} Point 点对象
 * @param {point2} Point 点对象
 * @returns {Number} 两点之间距离，单位为米
 */
GeoUtils.getDistance = function (point1, point2) {
  // 判断类型
  if (!(point1 instanceof Point) ||
    !(point2 instanceof Point)) {
    return 0;
  }

  point1.lng = _getLoop(point1.lng, -180, 180);
  point1.lat = _getRange(point1.lat, -74, 74);
  point2.lng = _getLoop(point2.lng, -180, 180);
  point2.lat = _getRange(point2.lat, -74, 74);

  let x1 = GeoUtils.degreeToRad(point1.lng);
  let y1 = GeoUtils.degreeToRad(point1.lat);
  let x2 = GeoUtils.degreeToRad(point2.lng);
  let y2 = GeoUtils.degreeToRad(point2.lat);

  return EARTHRADIUS * Math.acos((Math.sin(y1) * Math.sin(y2) + Math.cos(y1) * Math.cos(y2) * Math.cos(x2 - x1)));
}

/**
 * 计算多边形面或点数组构建图形的面积,注意：坐标类型只能是经纬度，且不适合计算自相交多边形的面积
 * @param {Polygon|Array<Point>} polygon 多边形面对象或者点数组
 * @returns {Number} 多边形面或点数组构成图形的面积
 */
GeoUtils.getPolygonArea = function (polygon) {
  // 检查类型
  if (!(polygon instanceof Polygon) &&
    !(polygon instanceof Array)) {
    return 0;
  }
  let pts;
  if (polygon instanceof Polygon) {
    pts = polygon.getPath();
  } else {
    pts = polygon;
  }

  if (pts.length < 3) { // 小于3个顶点，不能构建面
    return 0;
  }

  let totalArea = 0; // 初始化总面积
  let LowX = 0.0;
  let LowY = 0.0;
  let MiddleX = 0.0;
  let MiddleY = 0.0;
  let HighX = 0.0;
  let HighY = 0.0;
  let AM = 0.0;
  let BM = 0.0;
  let CM = 0.0;
  let AL = 0.0;
  let BL = 0.0;
  let CL = 0.0;
  let AH = 0.0;
  let BH = 0.0;
  let CH = 0.0;
  let CoefficientL = 0.0;
  let CoefficientH = 0.0;
  let ALtangent = 0.0;
  let BLtangent = 0.0;
  let CLtangent = 0.0;
  let AHtangent = 0.0;
  let BHtangent = 0.0;
  let CHtangent = 0.0;
  let ANormalLine = 0.0;
  let BNormalLine = 0.0;
  let CNormalLine = 0.0;
  let OrientationValue = 0.0;
  let AngleCos = 0.0;
  let Sum1 = 0.0;
  let Sum2 = 0.0;
  let Count2 = 0;
  let Count1 = 0;
  let Sum = 0.0;
  let Radius = EARTHRADIUS; // 6378137.0,WGS84椭球半径
  let Count = pts.length;
  for (let i = 0; i < Count; i++) {
    if (i === 0) {
      LowX = pts[Count - 1].lng * Math.PI / 180;
      LowY = pts[Count - 1].lat * Math.PI / 180;
      MiddleX = pts[0].lng * Math.PI / 180;
      MiddleY = pts[0].lat * Math.PI / 180;
      HighX = pts[1].lng * Math.PI / 180;
      HighY = pts[1].lat * Math.PI / 180;
    } else if (i === Count - 1) {
      LowX = pts[Count - 2].lng * Math.PI / 180;
      LowY = pts[Count - 2].lat * Math.PI / 180;
      MiddleX = pts[Count - 1].lng * Math.PI / 180;
      MiddleY = pts[Count - 1].lat * Math.PI / 180;
      HighX = pts[0].lng * Math.PI / 180;
      HighY = pts[0].lat * Math.PI / 180;
    } else {
      LowX = pts[i - 1].lng * Math.PI / 180;
      LowY = pts[i - 1].lat * Math.PI / 180;
      MiddleX = pts[i].lng * Math.PI / 180;
      MiddleY = pts[i].lat * Math.PI / 180;
      HighX = pts[i + 1].lng * Math.PI / 180;
      HighY = pts[i + 1].lat * Math.PI / 180;
    }
    AM = Math.cos(MiddleY) * Math.cos(MiddleX);
    BM = Math.cos(MiddleY) * Math.sin(MiddleX);
    CM = Math.sin(MiddleY);
    AL = Math.cos(LowY) * Math.cos(LowX);
    BL = Math.cos(LowY) * Math.sin(LowX);
    CL = Math.sin(LowY);
    AH = Math.cos(HighY) * Math.cos(HighX);
    BH = Math.cos(HighY) * Math.sin(HighX);
    CH = Math.sin(HighY);
    CoefficientL = (AM * AM + BM * BM + CM * CM) / (AM * AL + BM * BL + CM * CL);
    CoefficientH = (AM * AM + BM * BM + CM * CM) / (AM * AH + BM * BH + CM * CH);
    ALtangent = CoefficientL * AL - AM;
    BLtangent = CoefficientL * BL - BM;
    CLtangent = CoefficientL * CL - CM;
    AHtangent = CoefficientH * AH - AM;
    BHtangent = CoefficientH * BH - BM;
    CHtangent = CoefficientH * CH - CM;
    AngleCos = (AHtangent * ALtangent + BHtangent * BLtangent + CHtangent * CLtangent) / (Math.sqrt(AHtangent * AHtangent + BHtangent * BHtangent + CHtangent * CHtangent) * Math.sqrt(ALtangent * ALtangent + BLtangent * BLtangent + CLtangent * CLtangent));
    AngleCos = Math.acos(AngleCos);
    ANormalLine = BHtangent * CLtangent - CHtangent * BLtangent;
    BNormalLine = 0 - (AHtangent * CLtangent - CHtangent * ALtangent);
    CNormalLine = AHtangent * BLtangent - BHtangent * ALtangent;
    if (AM !== 0) {
      OrientationValue = ANormalLine / AM;
    } else if (BM !== 0) {
      OrientationValue = BNormalLine / BM;
    } else {
      OrientationValue = CNormalLine / CM;
    }

    if (OrientationValue > 0) {
      Sum1 += AngleCos;
      Count1++;
    } else {
      Sum2 += AngleCos;
      Count2++;
    }
  }
  let tempSum1, tempSum2;
  tempSum1 = Sum1 + (2 * Math.PI * Count2 - Sum2);
  tempSum2 = (2 * Math.PI * Count1 - Sum1) + Sum2;
  if (Sum1 > Sum2) {
    if ((tempSum1 - (Count - 2) * Math.PI) < 1) {
      Sum = tempSum1;
    } else {
      Sum = tempSum2;
    }
  } else {
    if ((tempSum2 - (Count - 2) * Math.PI) < 1) {
      Sum = tempSum2;
    } else {
      Sum = tempSum1;
    }
  }
  totalArea = (Sum - (Count - 2) * Math.PI) * Radius * Radius;

  return totalArea; // 返回总面积
}

export default GeoUtils;
